/*
 * Copyright (C) 2015-2017 Red Hat, Inc.
 *
 * Written By: Gal Hammer <ghammer@redhat.com>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met :
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and / or other materials provided with the distribution.
 * 3. Neither the names of the copyright holders nor the names of their contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include "pvpanic.h"
#include "bugcheck.tmh"

static KBUGCHECK_CALLBACK_RECORD CallbackRecord;
static KBUGCHECK_REASON_CALLBACK_RECORD DumpCallbackRecord;

KBUGCHECK_CALLBACK_ROUTINE PVPanicOnBugCheck;
KBUGCHECK_REASON_CALLBACK_ROUTINE PVPanicOnDumpBugCheck;

VOID PVPanicOnDumpBSOD(KBUGCHECK_CALLBACK_REASON Reason, PVOID Data)
{
    if (!(g_pvPanicContext->SupportedFeature & PVPANIC_BSOD)) {
        g_pvPanicContext->bEmitEvent = TRUE;
        TraceEvents(TRACE_LEVEL_VERBOSE, DBG_POWER,
            "Without BSOD feature, write crashloaded event complete.");

        return;
    }

    if (!g_pvPanicContext->bDumpFileInit) {
        PvPanicWrite64(g_pvPanicContext->BaseAddress, PVPANIC_DUMP_FILE_INIT, PVPANIC_DUMP_FILE_INIT_OFFSET);
        TraceEvents(TRACE_LEVEL_VERBOSE, DBG_POWER,
            "Dump file init on host complete.");

        g_pvPanicContext->bDumpFileInit = TRUE;
    }

    if ((g_pvPanicContext->BaseAddress != NULL) && (Reason == KbCallbackDumpIo)) {
        PKBUGCHECK_DUMP_IO tempData = (PKBUGCHECK_DUMP_IO)Data;
        if (tempData->Type != KbDumpIoComplete) {
            ULONG left = tempData->BufferLength;
            PUCHAR tempBuffer = (PUCHAR)tempData->Buffer;

            while (left) {
                PHYSICAL_ADDRESS MemAllocatedPA = MmGetPhysicalAddress(tempBuffer);
                ULONG size = min(left, (PAGE_SIZE - ((ULONG64)MemAllocatedPA.QuadPart & (PAGE_SIZE - 1))));
                PvPanicWrite64(
                    g_pvPanicContext->BaseAddress, (ULONG64)MemAllocatedPA.QuadPart, PVPANIC_BUFFER_ADDRESS_OFFSET);
                PvPanicWrite64(g_pvPanicContext->BaseAddress, (ULONG64)size, PVPANIC_BUFFER_SIZE_OFFSET);
                left -= size;
                tempBuffer += size;
            }
        } else {
            g_pvPanicContext->bEmitEvent = TRUE;
            TraceEvents(TRACE_LEVEL_VERBOSE, DBG_POWER,
                "Write dump file to device complete.");
        }
    }
}

VOID PVPanicOnBugCheck(IN PVOID Buffer, IN ULONG Length)
{
    // Trigger the PVPANIC_PANICKED event if the crash dump isn't enabled,
    if ((Buffer != NULL) && (Length == sizeof(PVOID)) && !g_pvPanicContext->bEmitEvent) {
        PvPanicWrite64(Buffer, PVPANIC_PANICKED, PVPANIC_EVENT_OFFSET);
        TraceEvents(TRACE_LEVEL_VERBOSE, DBG_POWER,
            "Write panicked event complete.");
    }
}

VOID PVPanicOnDumpBugCheck(
    KBUGCHECK_CALLBACK_REASON Reason,
    PKBUGCHECK_REASON_CALLBACK_RECORD Record,
    PVOID Data,
    ULONG Length)
{
    UNREFERENCED_PARAMETER(Length);

    if (!g_pvPanicContext->bNotify) {
        PvPanicWrite64(g_pvPanicContext->BaseAddress, PVPANIC_CRASHLOADED, PVPANIC_EVENT_OFFSET);
        TraceEvents(TRACE_LEVEL_VERBOSE, DBG_POWER,
            "Write crashloaded event complete.");

        g_pvPanicContext->bNotify = TRUE;
    }

    PVPanicOnDumpBSOD(Reason, Data);

    // Deregister BugCheckReasonCallback after KbDumpIoComplete is recieved or BSOD is not supported.
    if (g_pvPanicContext->bEmitEvent) {
        KeDeregisterBugCheckReasonCallback(Record);
    }
}

VOID PVPanicRegisterBugCheckCallback(IN PVOID BaseAddress, IN PUCHAR Component)
{
    UNREFERENCED_PARAMETER(BaseAddress);
    BOOLEAN bBugCheck;

    KeInitializeCallbackRecord(&CallbackRecord);
    KeInitializeCallbackRecord(&DumpCallbackRecord);

    if (g_pvPanicContext->SupportedFeature & PVPANIC_PANICKED) {
        bBugCheck = KeRegisterBugCheckCallback(&CallbackRecord, PVPanicOnBugCheck,
            (PVOID)BaseAddress, sizeof(PVOID), Component);
        if (!bBugCheck) {
            TraceEvents(TRACE_LEVEL_ERROR, DBG_POWER,
                "Failed to register bug check callback function.");
        }
    }

    if (g_pvPanicContext->SupportedFeature & PVPANIC_CRASHLOADED) {
        bBugCheck = KeRegisterBugCheckReasonCallback(&DumpCallbackRecord,
            PVPanicOnDumpBugCheck, KbCallbackDumpIo, Component);
        if (!bBugCheck) {
            TraceEvents(TRACE_LEVEL_ERROR, DBG_POWER,
                "Failed to register bug check reason callback function.");
        }
    }
}

VOID PVPanicDeregisterBugCheckCallback()
{
    BOOLEAN bBugCheck;

    if (g_pvPanicContext->SupportedFeature & PVPANIC_PANICKED) {
        bBugCheck = KeDeregisterBugCheckCallback(&CallbackRecord);
        if (!bBugCheck) {
            TraceEvents(TRACE_LEVEL_ERROR, DBG_POWER,
                "Failed to unregister bug check callback function.");
        }
    }

    if (g_pvPanicContext->SupportedFeature & PVPANIC_CRASHLOADED) {
        bBugCheck = KeDeregisterBugCheckReasonCallback(&DumpCallbackRecord);
        if (!bBugCheck) {
            TraceEvents(TRACE_LEVEL_ERROR, DBG_POWER,
                "Failed to unregister bug check reason callback function.");
        }
    }
}
